package com.wlf.admin.common.config;

import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.extra.spring.SpringUtil;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import javax.sql.DataSource;
import java.io.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 功能描述: 基于JDBC实现一个简单的Map,主要是一个可持久化的Map
 * 性能不咋地，肯定比内存Map慢，但是为了分布式以及无中间件，这样搞也行。
 *
 * @author wlf 2023/8/28 9:55
 */
@Data
@Slf4j
public class JdbcMap implements Serializable {

    /**
     * map名称
     */
    private String mapName;

    /**
     * mapKey
     */
    private String mapKey;
    /**
     * mapValue
     */
    private byte[] mapValue;


    @Serial
    private static final long serialVersionUID = 1L;


    /**
     * MapName
     */
    public enum MapName {
        data, expire
    }

    /**
     * 获取基于数据库实现的一个持久化的Map，用来代替Redis
     *
     * @param name 这个参数多次获取不可重复，这边强制使用枚举来定义，因为枚举自带不重复特性
     */
    public static <T> Map<String, T> getMap(MapName name) {
        Db db = Db.use(SpringUtil.getBean(DataSource.class));
        final String tableName = "sys_map";
        final String mapName = "map_name";
        final String mapKey = "map_key";
        final String mapValue = "map_value";
        return new Map<String, T>() {
            @Override
            @SneakyThrows
            @SuppressWarnings("all")
            public T get(Object key) {
                List<JdbcMap> jdbcMaps = db.find(
                        Entity.create(tableName)
                                .set(mapName, name.name())
                                .set(mapKey, key)
                        , JdbcMap.class);
                if (jdbcMaps.isEmpty()) {
                    return null;
                }
                Object object = JDKSerializeUtil.deserialize(jdbcMaps.get(0).getMapValue());
                if (object == null) {
                    // 删除序列化出错的内容
                    remove(key);
                    return null;
                }
                return (T) object;
            }

            @Override
            @SneakyThrows
            public T put(String key, Object value) {
                if (containsKey(key)) {
                    db.update(
                            Entity.create(tableName)
                                    .set(mapValue, JDKSerializeUtil.serialize(value))
                            , Entity.create(tableName)
                                    .set(mapName, name.name())
                                    .set(mapKey, key)
                    );
                } else {
                    db.insert(
                            Entity.create(tableName)
                                    .set(mapName, name.name())
                                    .set(mapKey, key)
                                    .set(mapValue, JDKSerializeUtil.serialize(value))
                    );

                }
                return null;
            }

            @Override
            @SneakyThrows
            public T remove(Object key) {
                db.del(
                        Entity.create(tableName)
                                .set(mapName, name.name())
                                .set(mapKey, key)
                );
                return null;
            }

            @Override
            @SneakyThrows
            public Set<String> keySet() {
                List<JdbcMap> jdbcMaps = db.find(
                        Entity.create(tableName)
                                .set(mapName, name.name())
                        , JdbcMap.class);
                return jdbcMaps.stream().map(JdbcMap::getMapKey).collect(Collectors.toSet());
            }

            @Override
            @SneakyThrows
            public int size() {
                return (int) db.count(
                        Entity.create(tableName)
                                .set(mapName, name.name())
                );
            }

            @Override
            public boolean isEmpty() {
                return size() == 0;
            }

            @Override
            @SneakyThrows
            public boolean containsKey(Object key) {
                return db.count(
                        Entity.create(tableName)
                                .set(mapName, name.name())
                                .set(mapKey, key)
                ) > 0;
            }

            @Override
            public boolean containsValue(Object value) {
                return false;
            }

            @Override
            public void putAll(Map<? extends String, ? extends T> m) {
                m.forEach(this::put);
            }

            @Override
            @SneakyThrows
            public void clear() {
                db.del(
                        Entity.create(tableName)
                                .set(mapName, name.name())
                );
            }

            @Override
            @SneakyThrows
            @SuppressWarnings("all")
            public Collection<T> values() {
                List<JdbcMap> jdbcMaps = db.find(
                        Entity.create(tableName)
                                .set(mapName, name.name())
                        , JdbcMap.class);
                return jdbcMaps.stream().map(
                        i -> {
                            return (T) JDKSerializeUtil.deserialize(i.getMapValue());
                        }
                ).collect(Collectors.toList());
            }

            @Override
            @SneakyThrows
            @SuppressWarnings("all")
            public Set<Entry<String, T>> entrySet() {
                List<JdbcMap> jdbcMaps = db.find(
                        Entity.create(tableName)
                                .set(mapName, name.name())
                        , JdbcMap.class);
                return jdbcMaps.stream().map(
                        i -> {
                            Entry<String, T> stringTEntry = new Entry<String, T>() {
                                @Override
                                public String getKey() {
                                    return i.mapKey;
                                }

                                @Override
                                public T getValue() {
                                    Object object = JDKSerializeUtil.deserialize(i.getMapValue());
                                    if (object == null) {
                                        remove(i.mapKey);
                                        return null;
                                    }
                                    return (T) object;
                                }

                                @Override
                                public T setValue(Object value) {
                                    return null;
                                }

                                @Override
                                public boolean equals(Object o) {
                                    return false;
                                }

                                @Override
                                public int hashCode() {
                                    return 0;
                                }
                            };
                            return stringTEntry;
                        }
                ).collect(Collectors.toSet());
            }
        };
    }


    /**
     * 功能描述: 序列化工具类
     *
     * @author wlf 2024/7/23 16:24
     */
    static class JDKSerializeUtil {
        /**
         * 序列化
         */
        public static byte[] serialize(Object object) {
            ObjectOutputStream objectOutputStream;
            ByteArrayOutputStream byteArrayOutputStream;
            byte[] getByte = null;
            try {
                byteArrayOutputStream = new ByteArrayOutputStream();
                objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
                objectOutputStream.writeObject(object);
                getByte = byteArrayOutputStream.toByteArray();
            } catch (IOException e) {
                log.error("序列化出错!", e);
            }
            return getByte;
        }

        /**
         * 反序列化（字节数组 -> 对象）
         */
        public static Object deserialize(byte[] binaryByte) {
            ObjectInputStream objectInputStream;
            ByteArrayInputStream byteArrayInputStream;
            Object readObject = null;
            try {
                byteArrayInputStream = new ByteArrayInputStream(binaryByte);
                objectInputStream = new ObjectInputStream(byteArrayInputStream);
                readObject = objectInputStream.readObject();
            } catch (Exception e) {
                log.error("反序列化出错!", e);
            }
            return readObject;
        }
    }

}

